const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const { Types } = require('mongoose');
const utility = require('../../../utility');
const files = require('../../../files');
const fileElements = require('../../../file-elements');
const Docx = require('../adapter/docx');
const ParentReporter = require('../default-report');
const ProjectVersionSpliter = require('../../project-version-spliter');
const projectVersions = require('../../../project-versions');
const fileDefects = require('../../../defects');
// const InvokeRelation = require('../../../mongo/invoke-relation');
const { retrieveRelations } = require('../../../invoke-relations');

class DefaultReporter extends ParentReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.baseDir = __dirname;
    this.testcases = [];
    this.errorTests = [];
    this.funcs = [];
    this.uncoveredFuncs = [];
    this.failedTestsCount = 0;

    this.summary = {
      funcCounts: {
        total: 0, // 函数总数
        linesGt200: 0, // 统计语句>200行的函数个数
        fanOutGte7: 0, // 统计扇出数≥7的函数个数
        cycleComplexityGt10: 0, // 统计圈复杂度>10的函数个数
      },
    };
  }

  async generate(progressIndicator) {
    const [
      filesProgressIndicator,
      generateReportProgressIndictor1,
      generateReportProgressIndictor2,
      generateReportProgressIndictor3,
    ] = progressIndicator ? progressIndicator.split([0.5, 0.3, 0.1, 0.1]) : [null, null];
    const result = {
      cachePath: this.outputDir,
    };
    const projectCreateDate = moment(Types.ObjectId(this.project._id).getTimestamp()).format('YYYY-MM-DD');
    const toolInfo = require('../../../../../package.json');
    const toolVersion = toolInfo.version;
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    await Promise.all([
      this.spliteByFile(null, filesProgressIndicator),
    ]);
    const [staticAnalyzeVersion] = (await projectVersions.getRelationVersions(this.version._id))
      .filter((v) => v.versionType === projectVersions.versionType.staticAnalyze);
    const analyzeResult = {
      mandatory: 0,
      required: 0,
      advisory: 0,
    };
    let defects = [];
    let editedDefectsCount = 0;
    if (staticAnalyzeVersion) {
      const rootDirId = staticAnalyzeVersion.rootDirectoryId;
      const file = await files.getFile(rootDirId);
      analyzeResult.mandatory = file.defectsCount.mandatory;
      analyzeResult.required = file.defectsCount.required;
      analyzeResult.advisory = file.defectsCount.advisory;
      defects = await fileDefects.getDefects(staticAnalyzeVersion._id);
      defects.forEach((defect, idx) => {
        defect.id = `JT-${idx + 1}`;
        if (defect.status !== fileDefects.defectStatus.toBeEdit) {
          editedDefectsCount += 1;
        }
      });
    }

    const overview = await this.getDataAdapter().getVersionOverview(this.versionId);
    this.summary.sourceFileCount = overview.sourceFileCount;
    this.summary.headerFileCount = overview.headerFileCount;
    this.summary.numberOfLines = overview.numberOfLines;
    this.summary.sourceLines = overview.numberOfLines - overview.commentLines - overview.emptyLines;
    this.summary.commentLines = overview.commentLines;
    this.summary.emptyLines = overview.emptyLines;
    this.summary.commentRate = `${parseInt((overview.commentLines / overview.numberOfLines) * 10000, 10) / 100}%`;
    const totalDefectsCount = analyzeResult.mandatory + analyzeResult.required + analyzeResult.advisory;
    await this.generateReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        toolVersion,
        testcases: this.testcases.map((t, idx) => {
          t.idx = idx + 1;
          return t;
        }),
        functionTests: Object.values(this.testcases.reduce((pre, t) => {
          if (pre[t.id] === undefined) {
            pre[t.id] = { id: t.id, testcases: [] };
          }
          pre[t.id].testcases.push(t);
          return pre;
        }, {})),
        errorTests: this.errorTests,
        reportDate: moment().format('YYYYMMDD'),
        totalDefectsCount,
        editedDefectsCount,
        analyzeResult,
        failedTestsCount: this.failedTestsCount,
        funcs: this.funcs.map((f, idx) => {
          f.idx = idx + 1;
          return f;
        }),
        uncoveredFuncs: this.uncoveredFuncs.map((f, idx) => {
          f.ucIdx = idx + 1;
          return f;
        }),
        defects,
      },
      `${path.join(this.versionDir, `${this.isIntegrationTest() ? '集成' : '单元'}测试报告`)}`,
      'summary-test-report'
    );
    generateReportProgressIndictor1.finish();
    await this.generateReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        toolVersion,
        projectCreateDate,
        testcases: this.testcases,
        summary: this.summary,
        functionTests: Object.values(this.testcases.reduce((pre, t) => {
          if (pre[t.id] === undefined) {
            pre[t.id] = { id: t.id, testcases: [] };
          }
          pre[t.id].testcases.push(t);
          return pre;
        }, {})),
        rep1: '“{”和“}”',
      },
      `${path.join(this.versionDir, `${this.isIntegrationTest() ? '集成' : '单元'}测试说明`)}`,
      'summary-test-desc',
    );
    generateReportProgressIndictor2.finish();
    await this.generateReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        toolVersion,
        funcs: this.funcs.map((f, idx) => {
          f.idx = idx + 1;
          return f;
        }),
      },
      `${path.join(this.versionDir, `${this.isIntegrationTest() ? '集成' : '单元'}测试计划`)}`,
      'summary-test-plan'
    );
    generateReportProgressIndictor3.finish();
    result.reportPath = path.join(this.outputDir, `${this.isIntegrationTest() ? '集成' : '单元'}测试报告.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async handleFile(file, versionDir) {
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem, functions } = res;
    ParentReporter.getSummaryOfFile(functions);
    await utility.arrayChunkOperation(
      functions.filter((func) => {
        if (fileElements.isTestableElement(func.kind)) {
          this.summary.funcCounts.total += 1;
        }
        const testsFilter = func.tests.reduce((pre, test) => {
          if (pre) {
            return pre;
          }
          return test.resultKindName === 'succeed'
            || test.resultKindName === 'nonStrictSucceed'
            || test.resultKindName === 'noResult'
            || test.resultKindName === 'failed';
        }, false);
        return fileElements.isTestableElement(func.kind) && testsFilter;
      }),
      1,
      async (func) => {
        if (func.numberOfLines > 200) {
          this.summary.funcCounts.linesGt200 += 1;
        }
        if (func.fanOut >= 7) {
          this.summary.funcCounts.fanOutGte7 += 1;
        }
        if (func.cycleComplexity > 10) {
          this.summary.funcCounts.cycleComplexityGt10 += 1;
        }
        let funcIdx = 0;
        const tests = this.getTestDetailsOfFunction(typeSystem, func);
        let testCount = 0;
        let failedCount = 0;
        let invokeRelations;
        let funcNameMap;
        if (this.isIntegrationTest()) {
          // invokeRelations = await InvokeRelation.find({ functionId: func._id }).lean();
          invokeRelations = await retrieveRelations(func._id, {
            sort: true,
            completeDepth: true,
          });
          funcNameMap = invokeRelations.reduce((funcMap, relation) => {
            const {
              toFuncId, toFuncName, fromFuncId, functionId,
            } = relation;
            if (toFuncId && toFuncName) {
              funcMap[toFuncId.toString()] = toFuncName;
            }
            if (fromFuncId.toString() === functionId.toString()) {
            // 入口函数
              funcMap[fromFuncId.toString()] = func.functionName;
            }

            return funcMap;
          }, {});
        }
        tests.forEach((test) => {
          if (test.resultKind !== 'succeed'
          && test.resultKind !== 'nonStrictSucceed'
          && test.resultKind !== 'noResult'
          && test.resultKind !== 'failed'
          ) {
            return;
          }
          const originalTestId = test.testId;
          funcIdx += 1;
          test.id = `S${this.isIntegrationTest() ? 'I' : 'U'}TP_${this.isIntegrationTest() ? 'JK' : 'GN'}_${file.fileName}_${func.functionName}功能测试`;
          test.name = `${func.functionName}_${funcIdx}`;
          test.testId = `S${this.isIntegrationTest() ? 'I' : 'U'}TP_${this.isIntegrationTest() ? 'JK' : 'GN'}_${file.fileName}_${func.functionName}_${funcIdx}`;
          test.functionName = func.functionName;
          const newInputDetails = [];
          const newOutputDetails = [];
          if (utility.isArrayType(test.detail.input)) {
            let isParam = false;
            let inputCount = 0;
            for (let i = 0, len = test.detail.input.length; i < len; i += 1) {
              const input = test.detail.input[i];
              // 处理变量值显示格式
              if (input.value !== '') {
                input.value = ` = ${input.value}`;
              }
              if (input.category) {
                isParam = ['形参', '全局变量'].includes(input.category);
              }
              if (input.variable) {
                inputCount += 1;
              }
              if (input.category && input.variable) {
                newInputDetails.push({
                  category: `${input.category}:`,
                  value: '',
                  variable: '',
                  isParam,
                });
                newInputDetails.push({
                  category: '',
                  value: input.value,
                  variable: input.variable,
                  isParam,
                });
              } else {
                input.isParam = isParam;
                newInputDetails.push(input);
              }
            }
            test.detail.inputCount = inputCount;
          }
          if (utility.isArrayType(test.detail.output)) {
            test.detail.output.forEach((output) => {
            // eslint-disable-next-line no-control-regex
              output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
              // eslint-disable-next-line no-nested-ternary
              output.value = output.value !== ''
                ? ` = ${output.value}`
                : (output.result !== '' ? ` = ${output.result}` : '');
              // 处理变量值显示格式
              if (output.result !== '') {
                output.result = ` = ${output.result}`;
              }
              if (output.category && output.variable) {
                newOutputDetails.push({
                  category: `${output.category}:`,
                  value: '',
                  result: '',
                  variable: '',
                });
                newOutputDetails.push({
                  category: '',
                  value: output.value,
                  result: output.result,
                  variable: output.variable,
                });
              } else {
                newOutputDetails.push(output);
              }
            });
          }
          test.detail.input = newInputDetails;
          test.detail.output = newOutputDetails;
          test.result = '通过';
          test.errNo = '';
          if (test.resultKind === 'failed') {
            this.failedTestsCount += 1;
            test.result = '不通过';
            test.errNo = `${this.isIntegrationTest() ? 'J' : 'D'}WT-${this.failedTestsCount}`;
            this.errorTests.push(test);
            failedCount += 1;
          }
          if (this.isIntegrationTest()) {
            const invokes = invokeRelations
              .filter((ir) => ir.testCases && ir.testCases.find((tc) => tc.toString() === originalTestId.toString()))
              .reduce((invokes, ir) => {
                const fromFuncId = ir.fromFuncId.toString();
                invokes[fromFuncId] = invokes[fromFuncId] || [];
                invokes[fromFuncId].push(ir.toFuncName);
                return invokes;
              }, {});
            test.invokeDesc = Object.keys(invokes).reduce((arr, fromFuncId) => {
              const fromFuncName = funcNameMap[fromFuncId] || '未知函数';
              arr.push(`${fromFuncName}能够成功调用${invokes[fromFuncId].join('、')}`);
              return arr;
            }, []).join('，');
          }
          this.testcases.push(test);
          testCount += 1;
        });
        const funcSummary = {
          ...this.getSummaryOfFunction(func),
          functionName: func.functionName,
          functionName1: `${file.fileName}::${func.functionName}`,
          functionName2: `${file.fileName}_${func.functionName}`,
          testCount,
          failedCount,
        };
        this.funcs.push(funcSummary);
        const uncovered = [];
        if (funcSummary.statementCoverage !== '100%' && funcSummary.statementCoverage !== '-') {
          uncovered.push(`语句覆盖率为${funcSummary.statementCoverage}`);
        }
        if (funcSummary.branchCoverage !== '100%' && funcSummary.branchCoverage !== '-') {
          uncovered.push(`分支覆盖率为${funcSummary.branchCoverage}`);
        }
        if (funcSummary.mcdcCoverage !== '100%' && funcSummary.mcdcCoverage !== '-') {
          uncovered.push(`MCDC覆盖率为${funcSummary.mcdcCoverage}`);
        }
        if (uncovered.length > 0) {
          this.uncoveredFuncs.push({
            functionName: func.functionName,
            reason: uncovered.join(','),
          });
        }
      }
    );
  }

  generateReport(data, output, template) {
    return (async () => {
      output = `${output}.docx`;
      const tpl = new Docx(path.join(this.baseDir, 'template', 'docx', `${template}${this.isIntegrationTest() ? '-it' : ''}.docx`));
      await tpl.generate(
        data,
        output,
        true
      );
      return output;
    })();
  }

  static versionTypeSupport(vType) {
    return vType === projectVersions.versionType.unitTest || vType === projectVersions.versionType.integrationTest;
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
